package com.mojo.concurrent.concurrent_04_cas_atomic;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 线程安全问题的解决方案主要分为三种：
 * 1）阻塞同步方案：一般加锁的方式，如synchronized,ReentrantLock或者像Redis和ZK实现的分布式锁
 * 2）非阻塞同步方案：基于CAS或者使用CAS实现的原子来
 * 3）非同步方案：使用ThreadLocal本地存储
 * 针对这种场景题在不能使用锁的情况下，我们可以考虑使用原子类来保证多线程下用户下单数据的一致性：
 * <p>
 * 使用原子类atomicInteger通过CAS的同步非阻塞保证线程的安全;
 * 1、 先将数据库的商品库存缓存到Redis
 * 2、 将库存数量作为构造器参数，创建AtomicInteger对象
 * 3、 扣减库存时使用compareAndSet方案，原子操作，同时先判断库存是否充足
 *
 * @author <a href="mailto:mojo_jj@163.com">Mojo</a>
 */
public class Shopping {
    // 假设库存有5万件商品
    static AtomicInteger stock = new AtomicInteger(1000);

    public static void main(String[] args) throws InterruptedException {
        List<Thread> list = new ArrayList<Thread>();
        for (int i = 0; i < 1000; i++) {
            // 多个用户下订单
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                     // 生成1-10的随机数模拟用户的购买数量
                    shopping(new Random().nextInt(10)+1);
                    //shopping(1);
                }
            });
            list.add(thread);
        }
        for (Thread t : list) {
            t.start();
        }
        for (Thread t : list) {
            t.join();
        }
        System.out.println(stock.get());
    }

    private static void shopping(int num) {
        while (true) {
            int currentValue = stock.get();
            //判断库存是否不足
            if (currentValue < num) {
                throw new IllegalArgumentException("商品库存不足");
            }
            // CAS扣减库存
            if (stock.compareAndSet(currentValue, currentValue - num)) {
                System.out.println("购买成功:" + num + "件商品，剩余库存:" + stock.get());
                break;
            }
        }
    }

}
